// External module representing Karel's city

// internal class representing one field
class Field {
    
    // attributes
    private mIsWall: boolean;
    private mNumberOfMarks: number;

    // Constructor - creates empty field
    public constructor() {
        this.ClearField();
    }

    // Makes the field wall
    public MakeWall() {
        this.mIsWall = true;
        this.mNumberOfMarks = 0;
    }

    // Makes the field empty
    public ClearField(): void {
        this.mIsWall = false;
        this.mNumberOfMarks = 0;
    }

    // Puts mark on the field
    public PutMark(): void {
        this.mNumberOfMarks++;
    }

    // puts to the field a specific number of marks
    public PutMarks(count: number) {
        this.mNumberOfMarks = count;
    }

    // Removes mark from the field
    public PickMark(): void {
        if (this.mNumberOfMarks == 0)
            throw "Cannot pick mark.";
        this.mNumberOfMarks--;
    }

    // Returns whether the field is wall
    public IsWall(): boolean {
        return this.mIsWall;
    }

    // Returns number of marks on the field
    public GetNumberOfMarks(): number {
        return this.mNumberOfMarks;
    }
}

// The city class itself
export class City {

    // internal city size
    private mInternalSize: number;

    // the city itself
    private mCity: Array<Array<Field>>;

    // singleton instance
    private static mInstance: City = null;

    // Constructor
    public constructor() {
        // the default size
        this.mInternalSize = 10;
        this.CreateCity();
    }

    // singleton get property
    public static get Instance() {
        if (City.mInstance == null)
            City.mInstance = new City();
        return City.mInstance;
    }

    // returns the internal size of the city
    public get InternalSize(): number {
        return this.mInternalSize;
    }

    // returns the size of city with the walls around 
    public get RealSize(): number {
        return this.mInternalSize + 2;
    }

    // makes the city empty
    public ClearCity(): void {
        // create the fields
        for (var row = 1; row <= this.InternalSize; row++) {
            for (var col = 1; col <= this.InternalSize; col++) {
                this.mCity[row][col].ClearField();
            }
        }
    }

    // method for collecting all marks
    public CollectAllMarks(): void {
        for (var row = 1; row <= this.InternalSize; row++) {
            for (var col = 1; col <= this.InternalSize; col++) {
                if (this.IsWall(row, col) == false) {
                    this.mCity[row][col].ClearField();
                }
            }
        }
    }

    // changes the size of the city
    public ChangeSize(newSize: number): void {
        this.mInternalSize = newSize;
        this.CreateCity();
    }

    // makes the field wall
    public MakeWall(row: number, col: number): void {
        this.mCity[row][col].MakeWall();
    }

    // clears the field
    public ClearField(row: number, col: number): void {
        this.mCity[row][col].ClearField();
    }

    // returns whether the field is wall
    public IsWall(row: number, col: number): boolean {
        return this.mCity[row][col].IsWall();
    }

    // puts the mark on the field
    public PutMark(row: number, col: number): void {
        this.mCity[row][col].PutMark();
    }

    // picks the mark from the field
    public PickMark(row: number, col: number): void {
        this.mCity[row][col].PickMark();
    }

    // returns number of marks on the field
    public GetNumberOfMarks(row: number, col: number): number {
        return this.mCity[row][col].GetNumberOfMarks();
    }

    // checks whether the robot can pick a mark
    public CanPickMark(row: number, col: number): boolean {
        return this.mCity[row][col].GetNumberOfMarks() != 0;
    }

    // Exports the city configuration
    public Export(): string {
        // create the items
        var items = Array<string>();

        // iterate all fields
        for (var row = 1; row <= this.InternalSize; row++) {
            for (var col = 1; col <= this.InternalSize; col++) {
                // check if wall
                if (this.IsWall(row, col) == true) {
                    items.push("W");
                }
                else {
                    // else push the number of marks in the field
                    var markCount = this.GetNumberOfMarks(row, col);
                    items.push(markCount.toString());
                }
            }
        }

        // concat to string
        return items.join(",");
    }

    // Imports the city configuration from the supplied string record
    public Import(configuration: string): void {

        try {
            // first clear the city
            this.ClearCity();

            // split into items
            var items = configuration.split(",");

            // iterate all fields
            var index = 0;
            for (var row = 1; row <= this.InternalSize; row++) {
                for (var col = 1; col <= this.InternalSize; col++) {
                    var item = items[index];

                    // check wall
                    if (item.toUpperCase() == "W") {
                        this.MakeWall(row, col);
                    }
                    else {
                        // else put a specific number of marks
                        var markCount = parseInt(item);
                        if (isNaN(markCount) == false) {
                            this.PutMarks(row, col, markCount);
                        }
                    }

                    index++;
                }
            }
        }
        catch(e) {
            // TO-DO: do we need to throw exception or leave silently???
        }
    }

    // puts to the field a specific number of marks
    private PutMarks(row: number, col: number, count: number): void {
        this.mCity[row][col].PutMarks(count);
    }

    // creates the city
    private CreateCity(): void {
        // allocate the city
        this.mCity = new Array<Array<Field>>(this.RealSize);

        // create the fields
        for (var row = 0; row < this.RealSize; row++) {            
            // create the row
            this.mCity[row] = new Array<Field>(this.RealSize);
            for (var col = 0; col < this.RealSize; col++) {
                this.mCity[row][col] = new Field();
            }           
        }

        this.MakeWallsAroundCity();
    }

    // creates the walls around the city
    private MakeWallsAroundCity(): void {
        // rows
        for (var row = 0; row < this.RealSize; row++) {
            this.mCity[row][0].MakeWall();
            this.mCity[row][this.RealSize - 1].MakeWall();
        }

        // columns
        for (var col = 0; col < this.RealSize; col++) {
            this.mCity[0][col].MakeWall();
            this.mCity[this.RealSize - 1][col].MakeWall();
        }
    }
}
